Dylib Injection
0x00 前言
通过学习《macOS Control Bypasses》,这里对于dylib注入的方法做列举和总结,输出一个文档。以下内容不一定准确,欢迎大家提建议,发表你的看法。
0x01 Dylib注入方式
- 环境变量注入
- DYLD_INSERT_LIBRARIES 注入
- 动态注入
- Dylib Hijacking(动态库劫持)
- Dylib Proxying(动态库代理)
- dlopen劫持
- 进程注入
- Mach Task Port注入
1x01 DYLD_INSERT_LIBRARIES 注入
通过设置环境变量 DYLD_INSERT_LIBRARIES 来在程序启动前注入dylib,该方法存在一定限制:
- 对于设置了SUID位的二进制文件无效
- 包含
__RESTRICT/__restrict
分段的二进制文件无效 - 开启SIP状态并签名的情况下
- 对启用Hardened Rutime/CS_RESTRICT的应用无效
- 对启用了library validation的应用无效
- 注入的dylib需要有正确的签名
- 如果包含以下两种entitlement可以注入
- com.apple.security.cs.allow-dyld-environment-variables
- com.apple.security.cs.disable-library-validation
使用方法:DYLD_INSERT_LIBRARIES=example.dylib ./hello
1x02 Dylib Hijacking
利用动态加载器(dyld)加载共享库时的特性进行注入,限制条件同上。
LC_LOAD_WEAK_DYLIB
命令在加载动态库时如果找不到文件会继续运行,不会抛出异常LC_RPATH
会指定搜索目录,可以定义多个LC_LOAD_DYLIB/LC_LOAD_WEAK_DYLIB/LC_LOAD_UPWARD_DYLIB
指定的路径如果是@rpath/xxx.dylib
,程序在加载时会按顺序依次在LC_PATH
中搜寻
此时就存在两种场景:
- 应用使用
LC_LOAD_WEAK_DYLIB
命令但实际dylib不存在时,可以放置自己的dylib @rpath
搜索路径顺序中,如果前面的路径不存在dylib,可以在前面路径放置自己的dylib
1x03 Dylib Proxying
dylib劫持的另一种利用方式,LC_REEXPORT_DYLIB
可以以代理的方式加载dylib,导出相关的函数,由此可以在编译注入的dylib时,通过重命名原始dylib并让自己的dylib指向真实dylib,将被劫持的dylib以代理模式重新导出,避免程序崩溃,限制条件同上。
// 编译dylib,适配current_version和compatibility_version,这个具体的值需要自行更改
// -reexport_library 是以代理的方式加载存在的dylib,防止程序崩溃,也就是LC_REEXPORT_DYLIB
gcc -dynamiclib -current_version 1.0 -compatibility_version 1.0 -framework Foundation hijack.m -Wl,-reexport_library,"hijacked_file_path" -o hijack.dylib
// 默认情况代理加载的dylib是以@rpath的形式加载的,修改为绝对路径的形式
install_name_tool -change @rpath/xxxx.dylib "/xxx/xxx/hijacked_file_path" hijack.dylib
1x04 dlopen劫持
当应用使用dlopen函数且未指定完整路径时,dyld会搜索不同路径。
搜索路径顺序:
- $LD_LIBRARY_PATH (如果设置)
- $DYLD_LIBRARY_PATH (如果设置)
- 当前工作目录
- $DYLD_FALLBACK_LIBRARY_PATH (如果设置)
- $DYLD_FALLBACK_LIBRARY_PATH (如果未设置)
- $HOME/lib
- /usr/local/lib
- /usr/lib
- 当前目录
限制条件:
- 如果二进制文件设置了 setuid/setgid 位或者签名包含 entitlements,所有环境变量都会被忽略
- 对于受限制的二进制文件,只能使用完整路径调用 dlopen
- /usr/lib 目录受 SIP 保护,即使 root 用户也无法写入
1x05 Mach Task Port注入
Mach是macOS的微内核,负责基本任务如调度、线程管理、硬件接口、虚拟内存和进程间通信,使用task作为资源共享的最小单位,一个task可以包含多个线程,通信通过单向通道和端口(port)进行,存在一个特殊的端口是task port,获得task port的SEND权限可以完全控制目标进程,可以读写虚拟内存,创建/停止线程等。
task port访问限制:
开启SIP
如果应用有com.apple.security.get-task-allow权限,同用户级别的进程可以访问其task port
有com.apple.system-task-ports权限可以访问任何进程的task port(仅限Apple自身应用)
非Apple自身应用且未启用hardened runtime时,root可以获取其task port
注入shellcode步骤:
- 使用task_for_pid获取目标进程task port的SEND权限
- 在目标进程分配内存并写入shellcode
- 创建远程线程执行shellcode
注入dylib步骤:
- 需要先将Mach线程提升为POSIX线程
- 使用pthread_create_from_mach_thread创建线程
- 使用dlopen加载dylib
- 处理线程的双重特性(Mach和POSIX)
关键代码示例(不能直接拿来用):
// 1. 获取目标进程的task port
task_t remoteTask;
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
// 2. 在远程进程分配内存
mach_vm_address_t remoteStack64;
mach_vm_address_t remoteCode64;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
kr = mach_vm_allocate(remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE);
// 3. 写入shellcode
kr = mach_vm_write(remoteTask, remoteCode64, (vm_offset_t)shellcode, CODE_SIZE);
// 4. 设置内存权限
kr = vm_protect(remoteTask, remoteCode64, CODE_SIZE, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
// 5. 创建远程线程执行shellcode
thread_act_t remoteThread;
x86_thread_state64_t remoteThreadState64;
thread_create_running(remoteTask, x86_THREAD_STATE64,
(thread_state_t)&remoteThreadState64,
x86_THREAD_STATE64_COUNT,
&remoteThread);
0x02 注入维持思路
-
- 这两款工具本质上一样的,后者是论坛@QiuChenly在前者的基础上增加了一些功能
- 工具会在被注入的程序上插入一个
LC_LOAD_DYLIB
指令,修复相关数据,让程序在运行时主动去加载指定的dylib - 如果加上
--weak
会插入LC_LOAD_WEAK_DYLIB
指令 - 检测到签名也可以移除签名
环境变量注入方式
在
Info.plist
文件中添加环境变量信息<key>LSEnvironment</key> <dict> <key>DYLD_INSERT_LIBRARIES</key> <string>/Applications/xxxx.app/Contents/xxxx.dylib</string> </dict>
此方法需要移除程序的签名,需要刷新Info.plist的缓存
codesign --remove-signature /Applications/xxxx.app/Contents/MacOS/xxxx
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/ Support/lsregister -f /Application/xxxx.app
Dylib劫持或者代理方式
- 本身就是会自动加载
0x03 总结
SIP开启情况下,最好的方法就是insert_dylib和环境变量维持的方式,但是需要删除签名,在某些存在校验的应用上会无效,还有就是如果有程序自带帮助程序/系统扩展的XPC服务,有可能会部分功能失效,因为XPC服务可能会校验请求的客户端(如果没有校验那它有被XPC攻击的可能,那你研究研究没准儿能搞出个CVE)。
SIP关闭,那你随意玩耍,注入基本就不是大问题了,此状态下也可以试试frida,frida的原理应该就是通过进程注入的方式在程序中注入javascript引擎,然后再用进程间通信机制来操作执行代码。